### -*- coding: utf-8 -*- #############################################
# Разработано Паэглит Арвидом (http://arvid.dp.ua/)
# Все права защищены, 2007
#
# Developed by Paeglit Arvid (http://arvid.dp.ua/)
# All right reserved, 2007
#######################################################################
"""Data class for the Zope 3 based diplom package

$Id: variant.py 66 2007-12-28 23:05:19Z Arvi3D $
"""
__author__  = "Arvid"
__license__ = "GPL"
__version__ = "$Revision: 66 $"
__date__ = "$Date: 2007-12-29 01:05:19 +0200 (Sat, 29 Dec 2007) $"

from zope.schema.fieldproperty import FieldProperty
from zope.interface import implements,implementedBy
from zope.cachedescriptors.property import CachedProperty
from persistent import Persistent
from zope.app.container.contained import Contained
from zope import schema
from zope.app import zapi

from math import sqrt, exp

from interfaces import IDataForExperiment, IVariant, ISelection, IEthalon, IDistributingFactory, IGenerated

CRITICAL_FREQUENCY = 0.3
CLASS_DISPLACEMENT = 1.0001
CLASS_COUNT_MIN = 7

class VariantStruct(object):
    number = 0.0
    frequency = 0
    posibility = 0.0
    func = 0.0

    def __init__(self, number, **kw):
        self.number = float(number)
        for n, v in kw.items():
            setattr(self, n, v)
        super(VariantStruct, self).__init__()

    def __str__(self):
        return "%s : %s : %s : %s" \
                % (self.number, self.frequency, self.posibility, self.func)

class Data(Persistent, Contained) :
    implements(IDataForExperiment, IVariant, ISelection, IEthalon, IGenerated)

    variant = ()

    classCount = FieldProperty(IVariant['classCount'])
    front = FieldProperty(IVariant['front'])
    back = FieldProperty(IVariant['back'])
    classLength = FieldProperty(IVariant['classLength'])

    dimension = FieldProperty(IGenerated['dimension'])

    firstDistr = FieldProperty(IEthalon['firstDistr'])
    @CachedProperty
    def _firstFactory(self):
        return zapi.getUtility(IDistributingFactory, name = self.firstDistr, context = self)

    secondDistr = FieldProperty(IEthalon['secondDistr'])
    @CachedProperty
    def _secondFactory(self):
        return zapi.getUtility(IDistributingFactory, name = self.secondDistr, context = self)

    @property
    def vector(self):
        return map(lambda v: v.number, self.variant)

    def __init__(self, vector = []):
        if vector:
            self.regenerateVariant(vector)
        super(Data, self).__init__()

    def regenerateVariant(self, vector):
        """calculate variant and save in attribute variant"""
        vector = vector[:]
        if not vector:
            return
        vector.sort()

        n = len(vector)
        values = []
        a, b = vector[0], vector[-1]
        #TODO: вынести подсчет количества классов в отдельную функцию и зарегать ее как утилиту
        m = max(CLASS_COUNT_MIN, int(sqrt(n) - 1))
        while(True):
            h = (b - a)/m*CLASS_DISPLACEMENT

            values = []
            for i in range(m):
                values.append(VariantStruct(a + i*h,
                                            frequency = 0,
                                            posibility = 0.0,
                                            func = 0.0
                                            ))

            for v in vector:
                p = int((v - a)/h)
                values[p].frequency = int(values[p].frequency + 1)
                values[p].posibility += 1.0/n

            for v in values:
                if v.posibility > CRITICAL_FREQUENCY:
                    m +=1
                    break
            else:
                break

        tSum = 0
        for v in values:
            tSum += v.posibility
            v.func = tSum

        self.variant = values
        self.classCount = m
        self.front = a
        self.back = b
        self.classLength = h
        self.dimension = n

    def nu(self, k):
        """get begin moment"""
        return sum(map(lambda v : v.frequency*(v.number)**k, self.variant))/self.dimension

    def mu(self, k):
        """get central moment"""
        nu1 = self.nu(1)
        return sum(map(lambda v : v.frequency*(v.number - nu1)**k, self.variant))/self.dimension

    @property
    def mean(self):
        """avarage"""
        return self.nu(1)

    def distributing(self, x):
        """distributing function based on variant"""
        if not self.variant or x < self.front:
            return 0
        elif x >= self.back:
            return 1
        else:
            p = int((x - self.front)/self.classLength)
            return self.variant[p].func

    def closeness(self, x):
        """closeness function based on variant"""
        if not self.variant or x < self.front or x >= self.back:
            return 0
        else:
            p = int((x - self.front)/self.classLength)
            return self.variant[p].posibility


    def var(self, zsuneniy = True):
        """get sqr of square distance"""
        if zsuneniy:
            return self.mu(2)
        else:
            return self.dimension/(self.dimension-1)*self.mu(2)

    def sigma(self, zsuneniy = True):
        """get square distance"""
        return sqrt(self.var(zsuneniy))

    def eksces(self, zsuneniy = True):
        """ get eksces"""
        E = self.mu(4)/(self.var()**2)
        if zsuneniy:
            return E
        else:
            n = self.dimension
            E = (n*n-1.0)/(n-2.0)/(n-3.0)*(6.0/(n+1) + (E-3))
            return E

    def teoreticalDistributing(self, x, weight, paramlist):
        """teoretical distributing function"""
        return weight*self._firstFactory.distributing(x, **dict(paramlist[0])) \
                + (1-weight)*self._secondFactory.distributing(x, **dict(paramlist[1]))

    def teoreticalCloseness(self, x, weight, paramlist):
        """teoretical closeness function"""
        return weight*self._firstFactory.closeness(x, **dict(paramlist[0])) \
                + (1-weight)*self._secondFactory.closeness(x, **dict(paramlist[1]))
